import os
import base64
import threading
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk
import requests
import pandas as pd
import chardet
import re
import json


class FeishuOCRApp:
    def __init__(self, root):
        self.root = root
        self.root.title("飞书OCR验证工具")
        self.root.geometry("1200x800")

        # 状态变量
        self.token = ""
        self.image_files = []
        self.data_file = ""
        self.results = []
        self.processing = False
        self.success_count = 0
        self.failure_count = 0
        self.ocr_cache = {}  # 缓存OCR结果
        self.log_marks = {}  # 存储日志标记位置
        self.current_highlight_tag = None  # 当前高亮标签
        self.column_groups = []  # 存储列组配置
        self.group_widgets = []  # 存储列组UI控件
        self.group_counter = 1  # 列组计数器

        # 创建界面
        self.create_auth_section()
        self.create_file_selection()
        self.create_processing_section()

        # 设置行高
        self.row_height = 25

        # 加载缓存
        self.load_cache()

        # 默认添加第一组列配置
        if not self.group_widgets:
            self.add_column_group("text1", "file1")

    def load_cache(self):
        """加载缓存的认证信息和列组配置"""
        cache_file = "feishu_cache.json"
        if os.path.exists(cache_file):
            try:
                with open(cache_file, "r") as f:
                    data = json.load(f)
                    self.app_id_entry.insert(0, data.get("app_id", ""))
                    self.app_secret_entry.insert(0, data.get("app_secret", ""))
                    self.token_entry.insert(0, data.get("token", ""))
                    self.token = data.get("token", "")

                    # 加载列组配置
                    if "column_groups" in data:
                        for group in data["column_groups"]:
                            self.add_column_group(group["text_col"], group["file_col"])

                    # 更新计数器为最大组号+1
                    if self.group_widgets:
                        self.group_counter = len(self.group_widgets) + 1
            except Exception as e:
                messagebox.showwarning("警告", f"加载缓存失败: {str(e)}")
                # 默认添加一组
                self.add_column_group("text", "file")

    def save_cache(self):
        """保存认证信息和列组配置到缓存"""
        cache_file = "feishu_cache.json"
        data = {
            "app_id": self.app_id_entry.get(),
            "app_secret": self.app_secret_entry.get(),
            "token": self.token_entry.get(),
            "column_groups": self.get_column_groups()  # 保存列组配置
        }
        try:
            with open(cache_file, "w") as f:
                json.dump(data, f)
        except Exception as e:
            messagebox.showwarning("警告", f"保存缓存失败: {str(e)}")

    def create_auth_section(self):
        """创建认证区域"""
        auth_frame = ttk.LabelFrame(self.root, text="飞书认证")
        auth_frame.pack(fill="x", padx=10, pady=5, ipadx=5, ipady=5)

        ttk.Label(auth_frame, text="App ID:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
        self.app_id_entry = ttk.Entry(auth_frame, width=40)
        self.app_id_entry.grid(row=0, column=1, padx=5, pady=5)

        ttk.Label(auth_frame, text="App Secret:").grid(row=0, column=2, padx=5, pady=5, sticky="w")
        self.app_secret_entry = ttk.Entry(auth_frame, width=40, show="*")
        self.app_secret_entry.grid(row=0, column=3, padx=5, pady=5)

        ttk.Label(auth_frame, text="Token:").grid(row=1, column=0, padx=5, pady=5, sticky="w")
        self.token_entry = ttk.Entry(auth_frame, width=40)
        self.token_entry.grid(row=1, column=1, padx=5, pady=5)

        self.get_token_btn = ttk.Button(auth_frame, text="获取Token", command=self.get_token)
        self.get_token_btn.grid(row=1, column=2, padx=5, pady=5)

    def create_file_selection(self):
        """创建文件选择区域 - 左右结构"""
        file_frame = ttk.LabelFrame(self.root, text="文件选择")
        file_frame.pack(fill="x", padx=10, pady=5, ipadx=5, ipady=5)

        # 创建左右两个框架
        left_frame = ttk.Frame(file_frame)
        left_frame.pack(side="left", fill="y", padx=5, pady=5)

        right_frame = ttk.Frame(file_frame)
        right_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5)

        # 左侧：文件选择区域
        # 图片选择
        img_btn_frame = ttk.Frame(left_frame)
        img_btn_frame.pack(fill="x", padx=5, pady=5)

        ttk.Button(img_btn_frame, text="选择图片", command=self.select_image).pack(side="left", padx=5)
        ttk.Button(img_btn_frame, text="选择文件夹", command=self.select_folder).pack(side="left", padx=5)
        self.img_path_label = ttk.Label(img_btn_frame, text="未选择图片")
        self.img_path_label.pack(side="left", padx=10)

        # 数据文件选择
        data_btn_frame = ttk.Frame(left_frame)
        data_btn_frame.pack(fill="x", padx=5, pady=5)

        ttk.Button(data_btn_frame, text="选择数据文件", command=self.select_data_file).pack(side="left", padx=5)
        self.data_path_label = ttk.Label(data_btn_frame, text="未选择数据文件")
        self.data_path_label.pack(side="left", padx=10)

        # 识别按钮
        self.process_btn = ttk.Button(left_frame, text="开始识别", command=self.start_processing)
        self.process_btn.pack(padx=5, pady=10, anchor="w")

        # 右侧：列组配置区域
        group_frame = ttk.LabelFrame(right_frame, text="列组配置")
        group_frame.pack(fill="both", expand=True, padx=5, pady=5, ipadx=5, ipady=5)

        # 创建带滚动条的容器
        scroll_container = ttk.Frame(group_frame)
        scroll_container.pack(fill="both", expand=True)

        # 创建Canvas和滚动条
        self.group_canvas = tk.Canvas(scroll_container, height=90)  # 固定高度显示三行
        scrollbar = ttk.Scrollbar(scroll_container, orient="vertical", command=self.group_canvas.yview)
        self.group_canvas.configure(yscrollcommand=scrollbar.set)

        scrollbar.pack(side="right", fill="y")
        self.group_canvas.pack(side="left", fill="both", expand=True)

        # 创建内部框架用于放置列组
        self.group_container = ttk.Frame(self.group_canvas)
        self.group_canvas.create_window((0, 0), window=self.group_container, anchor="nw")

        # 绑定配置事件
        self.group_container.bind("<Configure>", self.on_group_container_configure)

        # 添加标题行
        header_frame = ttk.Frame(self.group_container)
        header_frame.pack(fill="x", pady=(0, 5))

        ttk.Label(header_frame, text="组号", width=5).pack(side="left", padx=5)
        ttk.Label(header_frame, text="文本列名", width=12).pack(side="left", padx=5)
        ttk.Label(header_frame, text="文件列名", width=12).pack(side="left", padx=5)
        ttk.Label(header_frame, text="操作", width=5).pack(side="left", padx=5)

        # 添加组按钮
        add_btn_frame = ttk.Frame(group_frame)
        add_btn_frame.pack(fill="x", padx=5, pady=(0, 5))
        ttk.Button(add_btn_frame, text="新增列组", command=self.add_new_column_group).pack(side="left", padx=5)

    def on_group_container_configure(self, event):
        """更新Canvas的滚动区域"""
        self.group_canvas.configure(scrollregion=self.group_canvas.bbox("all"))

    def add_new_column_group(self):
        """新增一组列配置（带自动递增后缀）"""
        self.group_counter += 1
        self.add_column_group(f"text{self.group_counter}", f"file{self.group_counter}")
        self.save_cache()  # 保存缓存

    def add_column_group(self, text_col="text", file_col="file"):
        """添加一组列配置"""
        group_frame = ttk.Frame(self.group_container)
        group_frame.pack(fill="x", pady=2)

        # 组号
        group_label = ttk.Label(group_frame, text=f"{len(self.group_widgets) + 1}", width=5)
        group_label.pack(side="left", padx=5)

        # 文本列
        text_entry = ttk.Entry(group_frame, width=15)
        text_entry.pack(side="left", padx=5)
        text_entry.insert(0, text_col)

        # 文件列
        file_entry = ttk.Entry(group_frame, width=15)
        file_entry.pack(side="left", padx=5)
        file_entry.insert(0, file_col)

        # 删除按钮
        delete_btn = ttk.Button(group_frame, text="删除", width=5,
                                command=lambda: self.remove_column_group(group_frame))
        delete_btn.pack(side="left", padx=5)

        # 保存控件引用
        self.group_widgets.append({
            "frame": group_frame,
            "group_label": group_label,
            "text_entry": text_entry,
            "file_entry": file_entry,
            "delete_btn": delete_btn
        })

        # 更新滚动区域
        self.group_container.update_idletasks()
        self.group_canvas.configure(scrollregion=self.group_canvas.bbox("all"))
        # 保存缓存
        self.save_cache()

    def remove_column_group(self, group_frame):
        """删除一组列配置"""
        # 从UI中移除
        group_frame.destroy()

        # 从控件列表中移除
        for i, widget in enumerate(self.group_widgets):
            if widget["frame"] == group_frame:
                self.group_widgets.pop(i)
                break

        # 重新编号
        self.renumber_groups()

        # 如果没有组了，添加一个默认组
        if not self.group_widgets:
            self.add_column_group("text", "file")
        else:
            # 更新滚动区域
            self.group_container.update_idletasks()
            self.group_canvas.configure(scrollregion=self.group_canvas.bbox("all"))
            # 保存缓存
            self.save_cache()

    def renumber_groups(self):
        """重新编号所有列组"""
        for idx, widget in enumerate(self.group_widgets):
            widget["group_label"].config(text=f"{idx + 1}")

    def get_column_groups(self):
        """获取所有列组配置"""
        groups = []
        for widget in self.group_widgets:
            text_col = widget["text_entry"].get().strip()
            file_col = widget["file_entry"].get().strip()

            if text_col and file_col:
                groups.append({
                    "text_col": text_col,
                    "file_col": file_col
                })
        return groups

    def create_processing_section(self):
        """创建处理结果显示区域"""
        process_frame = ttk.Frame(self.root)
        process_frame.pack(fill="both", expand=True, padx=10, pady=5)

        # 缩略图区域
        thumb_frame = ttk.LabelFrame(process_frame, text="图片缩略图")
        thumb_frame.pack(side="left", fill="y", padx=5, pady=5, ipadx=5, ipady=5)
        thumb_frame.pack_propagate(False)
        thumb_frame.config(width=150)

        self.thumb_canvas = tk.Canvas(thumb_frame, width=140)
        self.thumb_canvas.pack(side="left", fill="both", expand=True)

        thumb_scroll = ttk.Scrollbar(thumb_frame, orient="vertical", command=self.thumb_canvas.yview)
        thumb_scroll.pack(side="right", fill="y")

        self.thumb_canvas.configure(yscrollcommand=thumb_scroll.set)
        self.thumb_inner_frame = ttk.Frame(self.thumb_canvas)
        self.thumb_canvas.create_window((0, 0), window=self.thumb_inner_frame, anchor="nw")

        def _on_thumb_config(event):
            self.thumb_canvas.configure(scrollregion=self.thumb_canvas.bbox("all"))

        self.thumb_inner_frame.bind("<Configure>", _on_thumb_config)

        # 结果列表区域
        result_frame = ttk.LabelFrame(process_frame, text="识别结果")
        result_frame.pack(side="left", fill="both", expand=True, padx=5, pady=5, ipadx=5, ipady=5)

        columns = ("group", "expected", "filename", "status")
        self.result_tree = ttk.Treeview(result_frame, columns=columns, show="headings", height=20)

        self.result_tree.heading("group", text="列组")
        self.result_tree.heading("expected", text="预期文本")
        self.result_tree.heading("filename", text="图片文件")
        self.result_tree.heading("status", text="识别状态")

        self.result_tree.column("group", width=100, anchor="w")
        self.result_tree.column("expected", width=200, anchor="w")
        self.result_tree.column("filename", width=150, anchor="w")
        self.result_tree.column("status", width=100, anchor="w")

        tree_scroll = ttk.Scrollbar(result_frame, orient="vertical", command=self.result_tree.yview)
        tree_scroll.pack(side="right", fill="y")
        self.result_tree.configure(yscrollcommand=tree_scroll.set)
        self.result_tree.pack(fill="both", expand=True)

        # 配置标签颜色
        self.result_tree.tag_configure("success", foreground="green")
        self.result_tree.tag_configure("failure", foreground="red")
        self.result_tree.tag_configure("waiting", foreground="black")

        # 绑定点击事件
        self.result_tree.bind("<<TreeviewSelect>>", self.on_result_select)

        # 状态统计
        stats_frame = ttk.Frame(result_frame)
        stats_frame.pack(fill="x", pady=5)

        ttk.Label(stats_frame, text="总处理数:").pack(side="left", padx=5)
        self.total_label = ttk.Label(stats_frame, text="0")
        self.total_label.pack(side="left", padx=5)

        ttk.Label(stats_frame, text="成功数:").pack(side="left", padx=5)
        self.success_label = ttk.Label(stats_frame, text="0", foreground="green")
        self.success_label.pack(side="left", padx=5)

        ttk.Label(stats_frame, text="失败数:").pack(side="left", padx=5)
        self.failure_label = ttk.Label(stats_frame, text="0", foreground="red")
        self.failure_label.pack(side="left", padx=5)

        # 日志区域
        log_frame = ttk.LabelFrame(process_frame, text="识别日志")
        log_frame.pack(side="left", fill="both", expand=True, padx=5, pady=5, ipadx=5, ipady=5)

        self.log_text = tk.Text(log_frame, wrap="word", state="disabled")
        # 配置高亮样式
        self.log_text.tag_config("highlight", background="yellow")
        log_scroll = ttk.Scrollbar(log_frame, command=self.log_text.yview)
        log_scroll.pack(side="right", fill="y")
        self.log_text.configure(yscrollcommand=log_scroll.set)
        self.log_text.pack(fill="both", expand=True)

    def get_token(self):
        """获取飞书Token"""
        app_id = self.app_id_entry.get()
        app_secret = self.app_secret_entry.get()

        if not app_id or not app_secret:
            messagebox.showerror("错误", "请填写App ID和App Secret")
            return

        url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
        headers = {"Content-Type": "application/json"}
        payload = {"app_id": app_id, "app_secret": app_secret}

        try:
            response = requests.post(url, headers=headers, json=payload)
            response.raise_for_status()
            data = response.json()

            if data.get("code") == 0:
                self.token = data["tenant_access_token"]
                self.token_entry.delete(0, tk.END)
                self.token_entry.insert(0, self.token)
                self.log("Token获取成功")
                self.save_cache()  # 保存缓存
            else:
                messagebox.showerror("错误", f"获取Token失败: {data.get('msg')}")
        except Exception as e:
            messagebox.showerror("错误", f"获取Token时发生错误: {str(e)}")

    def select_image(self):
        """选择单个图片文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[("图片文件", "*.jpg *.jpeg *.png *.bmp")]
        )
        if file_path:
            self.image_files = [file_path]
            self.img_path_label.config(text=file_path)

    def select_folder(self):
        """选择图片文件夹"""
        folder_path = filedialog.askdirectory()
        if folder_path:
            self.image_files = []
            for file in os.listdir(folder_path):
                if file.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp')):
                    self.image_files.append(os.path.join(folder_path, file))
            self.img_path_label.config(text=f"已选择 {len(self.image_files)} 张图片")

    def select_data_file(self):
        """选择数据文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[("数据文件", "*.csv *.xls *.xlsx")]
        )
        if file_path:
            self.data_file = file_path
            self.data_path_label.config(text=file_path)

    def start_processing(self):
        """开始处理流程"""
        if not self.token:
            messagebox.showerror("错误", "请先获取Token")
            return

        if not self.image_files:
            messagebox.showerror("错误", "请选择图片文件或文件夹")
            return

        if not self.data_file:
            messagebox.showerror("错误", "请选择数据文件")
            return

        # 获取列组配置
        column_groups = self.get_column_groups()
        if not column_groups:
            messagebox.showerror("错误", "请至少配置一组列")
            return

        try:
            # 读取数据文件
            if self.data_file.endswith('.csv'):
                # 检测文件编码
                with open(self.data_file, 'rb') as f:
                    result = chardet.detect(f.read())
                self.data_df = pd.read_csv(self.data_file, encoding=result['encoding'])
            else:  # Excel文件
                # 读取所有sheet并合并
                sheets_dict = pd.read_excel(self.data_file, sheet_name=None)
                self.data_df = pd.concat(sheets_dict.values(), ignore_index=True)

            # 初始化结果列表
            self.results = []
            self.result_tree.delete(*self.result_tree.get_children())
            self.thumb_inner_frame.destroy()
            self.thumb_inner_frame = ttk.Frame(self.thumb_canvas)
            self.thumb_canvas.create_window((0, 0), window=self.thumb_inner_frame, anchor="nw")

            # 添加结果到Treeview
            total_rows = 0
            for group in column_groups:
                text_col = group["text_col"]
                file_col = group["file_col"]
                group_name = f"{text_col},{file_col}"  # 使用列名作为组名

                # 验证列是否存在
                if text_col not in self.data_df.columns:
                    messagebox.showerror("错误", f"列 '{text_col}' 不存在于数据文件中")
                    return
                if file_col not in self.data_df.columns:
                    messagebox.showerror("错误", f"列 '{file_col}' 不存在于数据文件中")
                    return

                # 过滤空行
                df_group = self.data_df.dropna(subset=[text_col, file_col])
                total_rows += len(df_group)

                for _, row in df_group.iterrows():
                    self.result_tree.insert("", "end", values=(
                        group_name,
                        row[text_col],
                        row[file_col],
                        "等待中"
                    ), tags=("waiting",))

            # 更新统计
            self.total_label.config(text=str(total_rows))
            self.success_label.config(text="0")
            self.failure_label.config(text="0")
            self.success_count = 0
            self.failure_count = 0
            self.ocr_cache = {}
            self.log_marks = {}
            self.current_highlight_tag = None

            # 清空日志
            self.log_text.config(state="normal")
            self.log_text.delete(1.0, tk.END)
            self.log_text.config(state="disabled")

            # 显示缩略图
            self.show_thumbnails(column_groups)

            # 开始处理
            self.processing = True
            threading.Thread(target=self.process_images, args=(column_groups,), daemon=True).start()

        except Exception as e:
            messagebox.showerror("错误", f"处理数据失败: {str(e)}")

    def show_thumbnails(self, column_groups):
        """显示图片缩略图（按数据行分组）"""
        # 创建图片文件名到路径的映射
        img_map = {os.path.basename(path): path for path in self.image_files}

        # 按图片分组
        file_counts = {}
        for group in column_groups:
            file_col = group["file_col"]
            # 过滤空行
            df_group = self.data_df.dropna(subset=[file_col])
            for filename in df_group[file_col]:
                file_counts[filename] = file_counts.get(filename, 0) + 1

        # 设置缩略图高度（每个数据行的高度）
        row_height = self.row_height

        # 遍历每个唯一图片
        for filename, count in file_counts.items():
            img_path = img_map.get(filename)
            if not img_path:
                continue

            try:
                img = Image.open(img_path)
                img.thumbnail((100, 100))
                photo = ImageTk.PhotoImage(img)

                # 创建框架，高度为行数 * 行高
                frame = ttk.Frame(self.thumb_inner_frame, height=count * row_height)
                frame.pack(fill="x", pady=0)

                # 在框架中显示缩略图
                label = ttk.Label(frame, image=photo)
                label.image = photo  # 保持引用
                label.pack(pady=5)

                # 显示文件名
                ttk.Label(frame, text=filename, wraplength=130).pack()

            except Exception as e:
                self.log(f"无法加载缩略图: {filename} - {str(e)}")

        # 更新内部框架的大小
        self.thumb_inner_frame.update_idletasks()
        self.thumb_canvas.config(scrollregion=self.thumb_canvas.bbox("all"))

    def process_images(self, column_groups):
        """处理图片并进行OCR识别（按图片分组处理）"""
        # 创建图片文件名到路径的映射
        img_map = {os.path.basename(path): path for path in self.image_files}

        # 按图片文件分组处理
        processed_files = set()
        for group in column_groups:
            text_col = group["text_col"]
            file_col = group["file_col"]
            group_name = f"{text_col},{file_col}"  # 使用列名作为组名

            # 过滤空行
            df_group = self.data_df.dropna(subset=[text_col, file_col])

            # 按图片分组
            grouped = df_group.groupby(file_col)

            for filename, group_data in grouped:
                if not self.processing:
                    break

                # 如果这个文件已经处理过（在另一个组中），跳过OCR但继续处理匹配
                if filename in processed_files:
                    # 这个文件已经处理过，直接使用缓存的OCR结果
                    ocr_result = self.ocr_cache.get(filename, [])
                else:
                    # 第一次处理这个文件
                    processed_files.add(filename)

                    # 查找对应的图片文件
                    img_path = img_map.get(filename)
                    if not img_path:
                        # 更新该组所有行的状态为失败（图片未找到）
                        for _, row in group_data.iterrows():
                            self.update_row_status(
                                group_name,
                                row[text_col],
                                filename,
                                "失败: 图片未找到"
                            )
                            self.failure_count += 1
                        self.update_stats()
                        continue

                    # 记录日志标记位置
                    start_index = self.log_text.index(tk.END)
                    self.log(f"开始识别图片: {filename} (列组: {group_name})")
                    self.log(f"图片: {filename} 识别结果:")
                    self.log_marks[filename] = start_index  # 存储日志开始位置

                    # 进行OCR识别（如果尚未缓存）
                    if filename not in self.ocr_cache:
                        try:
                            ocr_result = self.ocr_recognize(img_path)
                            self.ocr_cache[filename] = ocr_result
                            for text in ocr_result:
                                self.log(f"  - {text}")
                        except Exception as e:
                            self.log(f"图片: {filename} 识别错误: {str(e)}")
                            self.ocr_cache[filename] = []
                    else:
                        ocr_result = self.ocr_cache[filename]

                # 处理该组中的每一行
                ocr_result = self.ocr_cache.get(filename, [])
                for _, row in group_data.iterrows():
                    expected_text = row[text_col]
                    # 改进的匹配逻辑：允许文本被拆分为多个部分
                    found = self.is_text_matched(expected_text, ocr_result)
                    status = "成功" if found else "失败"
                    if found:
                        self.success_count += 1
                    else:
                        self.failure_count += 1
                    self.update_row_status(
                        group_name,
                        expected_text,
                        filename,
                        status
                    )
                    self.update_stats()

        self.processing = False
        self.log("处理完成")

    def is_text_matched(self, expected_text, ocr_result):
        """
        改进的文本匹配逻辑：
        1. 允许预期文本被拆分为多个部分
        2. 忽略空格和特殊字符
        3. 支持部分匹配
        """
        # 预处理：移除空格和特殊字符
        clean_expected = re.sub(r'\s+', '', expected_text).lower()

        # 将OCR结果合并为一个字符串
        full_ocr_text = ''.join(ocr_result)
        clean_ocr = re.sub(r'\s+', '', full_ocr_text).lower()

        # 检查预处理后的预期文本是否在OCR文本中
        if clean_expected in clean_ocr:
            return True

        # 检查预期文本是否被拆分为多个部分（按单词拆分）
        words = re.split(r'\s+', expected_text)
        if len(words) > 1:
            # 检查所有单词是否按顺序出现在OCR结果中
            last_index = -1
            for word in words:
                clean_word = re.sub(r'\s+', '', word).lower()
                index = clean_ocr.find(clean_word, last_index + 1)
                if index == -1:
                    return False
                last_index = index
            return True

        return False

    def update_row_status(self, group_name, expected_text, filename, status):
        """更新结果列表中指定行的状态"""
        tag = "success" if "成功" in status else "failure" if "失败" in status else "waiting"
        for item in self.result_tree.get_children():
            values = self.result_tree.item(item, "values")
            if values[0] == group_name and values[1] == expected_text and values[2] == filename:
                self.result_tree.item(item, values=(group_name, expected_text, filename, status), tags=(tag,))
                break
        self.root.update()

    def ocr_recognize(self, image_path):
        """调用飞书OCR接口进行识别"""
        # 读取图片并转换为base64
        with open(image_path, "rb") as image_file:
            base64_image = base64.b64encode(image_file.read()).decode("utf-8")

        url = "https://open.feishu.cn/open-apis/optical_char_recognition/v1/image/basic_recognize"
        headers = {
            "Authorization": f"Bearer {self.token}",
            "Content-Type": "application/json"
        }
        payload = {"image": base64_image}

        response = requests.post(url, headers=headers, json=payload)
        response.raise_for_status()
        result = response.json()

        if result.get("code") != 0:
            raise Exception(result.get("msg", "OCR识别失败"))

        return result["data"]["text_list"]

    def update_stats(self):
        """更新统计信息"""
        self.success_label.config(text=str(self.success_count))
        self.failure_label.config(text=str(self.failure_count))
        self.root.update()

    def log(self, message):
        """添加日志信息"""
        self.log_text.config(state="normal")
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
        self.log_text.config(state="disabled")
        self.root.update()

    def on_result_select(self, event):
        """点击结果列表中的一行时，跳转到日志中对应的位置并高亮"""
        selected = self.result_tree.selection()
        if selected:
            item = selected[0]
            values = self.result_tree.item(item, "values")
            filename = values[2]

            if filename in self.log_marks:
                # 移除之前的高亮
                if self.current_highlight_tag:
                    self.log_text.tag_remove(self.current_highlight_tag, "1.0", tk.END)

                # 创建新的高亮标签
                self.current_highlight_tag = f"highlight_{filename}"
                self.log_text.tag_config(self.current_highlight_tag, background="yellow")

                # 查找并高亮相关日志
                mark_pos = self.log_marks[filename]
                self.log_text.config(state="normal")

                # 搜索"图片: {filename} 识别结果:"这一行
                start_index = mark_pos
                end_index = self.log_text.index(f"{start_index} lineend")

                # 高亮该行
                self.log_text.tag_add(self.current_highlight_tag, start_index, end_index)

                # 滚动到标记位置
                self.log_text.see(start_index)
                self.log_text.config(state="disabled")


if __name__ == "__main__":
    root = tk.Tk()
    app = FeishuOCRApp(root)
    root.mainloop()